package sshtool

import (
	"golang.org/x/crypto/ssh"
	"time"
	"fmt"
)

type Monitor struct {
	Client *ssh.Client
	Channel ssh.Channel
	Send chan []byte
	Out chan []byte
}


type ptyRequestMsg struct {
	Term     string
	Columns  uint32
	Rows     uint32
	Width    uint32
	Height   uint32
	Modelist string
}


func NewSSH(user, password, host string, port string) (*Monitor,error) {
	var (
		auth         []ssh.AuthMethod
		addr         string
		clientConfig *ssh.ClientConfig
		Clinet       *ssh.Client
		err          error
	)
	newSSh := Monitor{}
	// get auth method
	auth = make([]ssh.AuthMethod, 0)
	auth = append(auth, ssh.Password(password))

	clientConfig = &ssh.ClientConfig{
		User:    user,
		Auth:    auth,
		Timeout: 30 * time.Second,
	}

	// Clinet to ssh
	addr = fmt.Sprintf("%s:%s", host, port)

	if Clinet, err = ssh.Dial("tcp", addr, clientConfig); err != nil {
		panic(err)
	}
	defer Clinet.Close()
	// create channel
	channel, incomingRequests,err := Clinet.Conn.OpenChannel("session",nil)
	if err != nil {
		panic(err)
	}
	if err != nil {
		panic(err)
	}
	go func() {
		for req := range incomingRequests {
			if req.WantReply {
				req.Reply(false, nil)
			}
		}
	}()
	modes := ssh.TerminalModes{
		ssh.ECHO:          1,
		ssh.TTY_OP_ISPEED: 14400,
		ssh.TTY_OP_OSPEED: 14400,
	}
	var modeList []byte
	for k, v := range modes {
		kv := struct {
			Key byte
			Val uint32
		}{k, v}
		modeList = append(modeList, ssh.Marshal(&kv)...)
	}
	modeList = append(modeList, 0)
	req := ptyRequestMsg{
		Term:     "xterm",
		Columns:  40,
		Rows:     60,
		Width:    40 * 8,
		Height:   60 * 8,
		Modelist: string(modeList),
	}
	ok, err := channel.SendRequest("pty-req", true, ssh.Marshal(&req))
	if !ok || err != nil {
		panic(err)
	}
	ok, err = channel.SendRequest("shell", true, nil)
	if !ok || err != nil {
		panic(err)
	}
	defer channel.Close()
	newSSh.Channel = channel
	newSSh.Client = Clinet
	return &newSSh,nil
}

func (m *Monitor)Run(){
	go func() {
		rbuf := make([]byte, 1024)
		for {
			n, err := m.Channel.Read(rbuf)


			if err != nil {
				if err.Error() == "EOF"{
					continue
				}
				return
			}

			if n > 0 {
				m.Out <- rbuf
			}
		}
	}()
	go func(){
		for{
			select {
			case  mess := <- m.Send:
				_,err :=m.Channel.Write(mess)
				if err != nil{
					return
				}
			}
		}
	}()
}